﻿using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.Structure;
using System.ComponentModel;

namespace CellImageProcessing
{
    public enum CellType : int 
    {
        [Description("Tế bào tinh trùng")]
        Sperm = 1,
        [Description("Tế bào gốc trung mô")]
        MSC = 2,
        [Description("Nguyên bào sợi")]
        FBL = 3,
        [Description("Không phân biệt được")]
        None = 0, 
    } 

    public class CellData
    {
        #region DLL Imports
        [DllImport(@"CellAnalysis.dll", EntryPoint = "DetectCells")]
        private static extern int __DetectCells(IntPtr binaryInputImage, IntPtr contourImage, ref int left, ref int top, ref int rectWidth, ref int rectHeight);
        [DllImport(@"CellAnalysis.dll", EntryPoint = "SplitCells")]
        private static extern void __SplitCells(IntPtr input, IntPtr output, int block_size, double threshold, int boundary_size);
        [DllImport(@"CellAnalysis.dll", EntryPoint = "CoutingPixelValue")]
        private static extern int __CoutingPixelValue(IntPtr input, int grayValue);
        #endregion

        #region Methods
        public static int DetectCells(Image<Gray, Byte> binaryInputImage, IntPtr contourImagePtr, ref int left, ref int top, ref int width, ref int height)
        {
            int num = __DetectCells(binaryInputImage.Ptr, contourImagePtr, ref left, ref top, ref width, ref height);
            return num;
        }

        public static int DetectCells(Image<Gray, Byte> binaryInputImage, IntPtr contourImagePtr)
        {
            int l = 0;
            int t = 0;
            int w  = 0;
            int h = 0;
            
            int num = __DetectCells(binaryInputImage, contourImagePtr, ref l, ref t, ref w, ref h);
            return num;
        }

        public static void SplitCells(Image<Gray, Byte> inputImage, Image<Gray, Byte> outputImage, int block_size, double threshold, int boundary_size)
        {
            __SplitCells(inputImage.Ptr, outputImage.Ptr, block_size, threshold, boundary_size);
        }

        public static int CoutingPixelValue(Image<Gray, Byte> inputImage, int grayValue)
        {
            int value = __CoutingPixelValue(inputImage.Ptr, grayValue);
            return value;
        }
        #endregion

        #region Attributes
        public Image<Bgr, Byte> originalImage;		// original image
        public Image<Bgr, Byte> rgbImage;			// image in rgb after being resized // 00000000 : 0
        public Image<Gray, Byte> grayImage;			// image in grayscale   // 00000001 : 1
        public Image<Gray, Byte> binaryImage;		// image in black and white // 00000100 : 4
        public Image<Gray, Byte> edgeImage;			// image after detect edges // 00001000 : 8
        public Image<Gray, Byte> gridImage;			// image of grid in image   // 00010000 : 16
        public Image<Gray, Byte> bluredbackImage;   // image of grayscale after being blured background // 00100000 : 32
        public Image<Gray, Byte> erasedbackImage;   // grayscale image after being erased background    // 01000000 : 64
        public Image<Gray, Byte> detectedImage;		// image contains detected cells    // 10000000 : 128
        public Image<Gray, Byte> tempImage;
        public static int sampleWidth = 340;
        public static int sampleHeight = 380;
        public Rectangle biggestBoundingRect = new Rectangle(); 
        #endregion

        #region Methods
        // Resize bounding input Rect to new width and height
        static public Rectangle GetBoundingSample(Rectangle inputRect, int widthImage, int heightImage)
        {
            int width = sampleWidth;
            int height = sampleHeight;

            Rectangle newBoundingRect = new Rectangle();
            int l, t, r, b;
            int d, delta;

            l = inputRect.X;
            t = inputRect.Y;
            r = l + inputRect.Width;
            b = t + inputRect.Height;

            if (inputRect.Width < width)
            {
                d = width - inputRect.Width;
                delta = (int)Math.Ceiling(d * 1.0 / 2);
                if (l - delta < 0)
                {
                    l = 0;
                    r = l + width;
                }
                else
                {
                    if (r + delta > widthImage)
                    {
                        r = widthImage;
                        l = r - width;
                    }
                    else
                    {
                        l = l - delta;
                        r = l + width;
                    }
                }
            }
            else
            {
                d = inputRect.Width - width;
                delta = (int)Math.Ceiling(d * 1.0 / 2);
                l = l + delta;
                r = l + width;
            }

            if (inputRect.Height < height)
            {
                d = height - inputRect.Height;
                delta = (int)Math.Ceiling(d * 1.0 / 2);
                if (t - delta < 0)
                {
                    t = 0;
                    b = t + height;
                }
                else
                {
                    if (b + delta > heightImage)
                    {
                        b = heightImage;
                        t = b - height;
                    }
                    else
                    {
                        t = t - delta;
                        b = t + height;
                    }
                }
            }
            else
            {
                d = inputRect.Height - height;
                delta = (int)Math.Ceiling(d * 1.0 / 2);
                t = t + delta;
                b = t + height;
            }
            newBoundingRect.X = l;
            newBoundingRect.Y = t;
            newBoundingRect.Width = width;
            newBoundingRect.Height = height;

            return newBoundingRect;
        }

        // R2 is in R1, convert R2 to the same coordinate system of R1.
        static public Rectangle MappingBoundingRect(Rectangle R1, Rectangle R2)
        {
            Rectangle Rnew = new Rectangle();
            Rnew.Width = R2.Width;
            Rnew.Height = R2.Height;
            Rnew.X = R1.X + R2.X;
            Rnew.Y = R1.Y + R2.Y;
            return Rnew;
        } 

        // Initialize
        public bool Initialize(string fileName)
        {
            try
            {
                originalImage = new Image<Bgr, byte>(fileName);
            }
            catch (Exception)
            {
                throw new Exception("Can not open this image");
            }
            // resize the original image
            this.rgbImage = ImageProcessing.Resize(originalImage, 1024, 768);

            int width = rgbImage.Width;
            int height = rgbImage.Height;

            grayImage = new Image<Gray, Byte>(width, height);
            grayImage = rgbImage.Convert<Gray, Byte>();

            binaryImage = new Image<Gray, Byte>(width, height);
            edgeImage = new Image<Gray, Byte>(width, height);
            gridImage = new Image<Gray, Byte>(width, height);
            bluredbackImage = new Image<Gray, Byte>(width, height);
            erasedbackImage = new Image<Gray, Byte>(width, height);
            detectedImage = new Image<Gray, Byte>(width, height);
            tempImage = new Image<Gray, Byte>(width, height);
            
            return true;
        }

        static public CellData CopyFrom(CellData inputCellData, Rectangle croppedRect)
        {
            CellData newCell = new CellData();
            
            //newCell.originalImage = new Image<Bgr, byte>(croppedRect.Width, croppedRect.Height);
            inputCellData.originalImage.ROI = croppedRect;
            newCell.originalImage = inputCellData.originalImage.Copy();
            inputCellData.originalImage.ROI = Rectangle.Empty;
            
            inputCellData.rgbImage.ROI = croppedRect;
            newCell.rgbImage = inputCellData.rgbImage.Copy();
            inputCellData.rgbImage.ROI = Rectangle.Empty;
            
            inputCellData.grayImage.ROI = croppedRect;
            newCell.grayImage = inputCellData.grayImage.Copy();
            inputCellData.grayImage.ROI = Rectangle.Empty;

            inputCellData.edgeImage.ROI = croppedRect;
            newCell.edgeImage = inputCellData.edgeImage.Copy();
            inputCellData.edgeImage.ROI = Rectangle.Empty;

            inputCellData.binaryImage.ROI = croppedRect;
            newCell.binaryImage = inputCellData.binaryImage.Copy();
            inputCellData.binaryImage.ROI = Rectangle.Empty;

            inputCellData.gridImage.ROI = croppedRect;
            newCell.gridImage = inputCellData.gridImage.Copy();
            inputCellData.gridImage.ROI = Rectangle.Empty;

            inputCellData.detectedImage.ROI = croppedRect;
            newCell.detectedImage = inputCellData.detectedImage.Copy();
            inputCellData.detectedImage.ROI = Rectangle.Empty;

            inputCellData.tempImage.ROI = croppedRect;
            newCell.tempImage = inputCellData.tempImage.Copy();
            inputCellData.tempImage.ROI = Rectangle.Empty;

            newCell.bluredbackImage = new Image<Gray, Byte>(croppedRect.Width, croppedRect.Height);
            newCell.erasedbackImage = new Image<Gray, Byte>(croppedRect.Width, croppedRect.Height);
            return newCell;
        }

        public int CountCell()
        {
            if (this != null)
            {
                try
                {
                    // contrast stretch the GRAYSCALE image
                    ImageProcessing.ConstrastStretch(this.rgbImage.Convert<Gray, Byte>(), this.grayImage);
                    // detect edges in grayscale image using Sobel
                    ImageProcessing.SobelFilter(this.grayImage, this.edgeImage);
                    // top-hat transform
                    //CvInvoke.cvMorphologyEx(this.edgeImage.Ptr, this.tempImage.Ptr, new IntPtr(), new IntPtr()
                    //    , Emgu.CV.CvEnum.CV_MORPH_OP.CV_MOP_TOPHAT, 1);
                    // binarize the edge image                    
                    this.binaryImage = ImageProcessing.BinarizeImage(this.tempImage, new Gray(255.0));
                    //this.binaryImage = ImageProcessing.BinarizeImage(this.edgeImage, new Gray(255.0));
                    // get grid image in the binary image of edge image
                    ImageProcessing.GetGrid(this.binaryImage, this.gridImage, 5.5);
                    // blur the grid in the grayscale image
                    ImageProcessing.BlurGrid(this.grayImage, this.bluredbackImage, this.gridImage);
                    // erase the background of the blured grayscale image
                    //ImageProcessing.EraseBackgroundWithThreshold1(this.bluredbackImage, this.erasedbackImage, backgroundGrayThres - 29);
                    ImageProcessing.EraseBackground(this.bluredbackImage, this.erasedbackImage);
                    //ImageProcessing.EraseBackgroundWithThreshold2(cellData.bluredbackImage, cellData.erasedbackImage, backgroundGrayThres, 4);
                    // remove grid in the non-back Image
                    ImageProcessing.RemoveGrid(ImageProcessing.BinarizeImage(this.erasedbackImage, new Gray(255.0)), this.binaryImage, this.gridImage);
                    // detect cells
                    this.detectedImage = this.grayImage.Copy();
                    int numOfCells = CellData.DetectCells(this.binaryImage, this.detectedImage.Ptr);
                    // show the results
                    return numOfCells;
                }
                catch (Exception ex)
                {
                    throw ex;                    
                }
            }
            else
            {
                return -1;
            }
        }

        public CellType ClassifyCell()
        {
            if (this != null)
            {
                try
                {
                    // contrast stretch the GRAYSCALE image
                    ImageProcessing.ConstrastStretch(this.rgbImage.Convert<Gray, Byte>(), this.grayImage);
                    // detect edges in grayscale image using Sobel
                    ImageProcessing.SobelFilter(this.grayImage, this.edgeImage);
                    // binarize the edge image

                    this.binaryImage = ImageProcessing.BinarizeImage(this.edgeImage, new Gray(255.0));
                    // get grid image in the binary image of edge image
                    ImageProcessing.GetGrid(this.binaryImage, this.gridImage, 5.5);
                    // remove grid in the non-back Image
                    Image<Gray, Byte> tempImage = new Image<Gray, byte>(this.binaryImage.Width, this.binaryImage.Height);
                    ImageProcessing.RemoveGrid(this.binaryImage, tempImage, this.gridImage);
                    this.binaryImage = tempImage.Copy();
                    this.detectedImage = this.grayImage.Copy();
                    int X = 0, Y = 0, Width = 0, Height = 0;
                    // detect cells
                    int numOfCells = CellData.DetectCells(this.binaryImage, this.detectedImage.Ptr,
                        ref X,
                        ref Y,
                        ref Width,
                        ref Height);
                    this.biggestBoundingRect = new Rectangle(X, Y, Width, Height);
                    int maxArea = this.binaryImage.Width * this.binaryImage.Height;
                    int area = this.biggestBoundingRect.Width * this.biggestBoundingRect.Height;

                    if (area < 0.025 * maxArea)
                    {
                        this.detectedImage = this.grayImage.Copy(this.biggestBoundingRect);
                        return CellType.Sperm;
                    }
                    else
                    {
                        CellData cell2 = CellData.CopyFrom(this, this.biggestBoundingRect);
                        //cell2.tempImagetempImage->CopyOf(cell2->SplitCells(cell2->binaryImage->GetImage()));
                        CellData.SplitCells(cell2.binaryImage, cell2.tempImage, 9, 0.7, 3);
                        CvInvoke.cvMorphologyEx(cell2.tempImage.Ptr, cell2.binaryImage.Ptr, new IntPtr(), new IntPtr(), Emgu.CV.CvEnum.CV_MORPH_OP.CV_MOP_CLOSE, 1);

                        cell2.detectedImage = cell2.grayImage.Copy();

                        // detect cells
                        numOfCells = CellData.DetectCells(cell2.binaryImage, cell2.detectedImage.Ptr,
                        ref X,
                        ref Y,
                        ref Width,
                        ref Height);
                        cell2.biggestBoundingRect = new Rectangle(X, Y, Width, Height);
                        this.biggestBoundingRect = CellData.MappingBoundingRect(this.biggestBoundingRect, cell2.biggestBoundingRect);
                        this.biggestBoundingRect = CellData.GetBoundingSample(this.biggestBoundingRect, this.grayImage.Width, this.grayImage.Height);
                        this.detectedImage = this.binaryImage.Copy(this.biggestBoundingRect);

                        area = CellData.CoutingPixelValue(this.detectedImage, 255);
                        maxArea = this.detectedImage.Width * this.detectedImage.Height;
                        this.detectedImage = this.grayImage.Copy(this.biggestBoundingRect);
                        if (area <= 0.25 * maxArea)
                        {
                            return CellType.MSC;
                        }
                        else
                        {
                            return CellType.FBL;
                        }
                    }
                    
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            else
            {
                return CellType.None;
            }
        }

        #endregion
    }
}